var tempError = minErr('templateHelper');
var $attrTag = 'jcinner-temp-status',
    $jcData = "jc" + VERSION.replace(/[\.\-_]/g, "");

var AP = Array.prototype;

function removeChildrenPathBind(id, path) {

    var reg = new RegExp("^" + (path + '.').replace(/\./g, "\\.")),
        watchPB = $watchPB(id),
        thPB = tempHelper.$pb(id),
        watchPath = path + ".length";
    watchPB.eventNamesWithReg(reg).forEach(function(eventName) {
        if (eventName !== watchPath)
            watchPB.unbind(eventName)
    })
    thPB.eventNamesWithReg(reg).forEach(function(eventName) {
        thPB.unbind(eventName)
    })
}

function tempHelperPupEvent(id, path, message) {
    tempHelper.$pb(id).publish(path, message)
    if (Array.isArray(message)) {

        $watchPB(id).publish(path + '.length', message)
    }
    $watchPB(id).publish(path, message)
}

var fakeArrayMethod = {
    set: function(k, v) { //god method ... bad implement
        if (this[k] !== v) {
            if (typeof v === 'object') {
                v.$index = k
                jc.mix(this[k], v)
            } else {
                this[k] = v
                this.$index = k
            }
            tempHelperPupEvent(this.$id, this.$parentPath + "." + k, this[k])
        }
    },
};

"splice,pop,shift,unshift,push".replace(rword, function(method) {
    fakeArrayMethod[method] = function() {
        var re = AP[method].apply(this, arguments)
        removeChildrenPathBind(this.$id, this.$parentPath, this)
        tempHelperPupEvent(this.$id, this.$parentPath, this)
        return re
    }
})

function valueBind(thisData, id, path, targetPath, val, ref) {
    var tpathStr = targetPath.join(".")
    tempHelper.$pb(id).bind(tpathStr, (function(value) {
        arrPathVal(thisData, this.path, value)
    }).bind({
        path: path
    }))
    try {
        //if ("value" in Object.getOwnPropertyDescriptor(ref.obj, ref.ref)) {
        var getAndSet = function(value) {
            if (arguments.length === 0)
                return val
            else {
                if (value !== val) {
                    if (value instanceof Array) {
                        mix(value, fakeArrayMethod)
                        value.$id = id
                        value.$parentPath = val.$parentPath
                    }
                    val = value
                    tempHelperPupEvent(id, tpathStr, value)
                }
            }
        }
        Object.defineProperty(ref.obj, ref.ref, {
            get: getAndSet,
            set: getAndSet
        })
        //}
    } catch (e) {
        tempError("the key to be binded should not be undefined. path:{1}", path.join('.'))
    }
}


var tempHelperObj = {
    _data: function(key, value) {
        var isObj = getType(key) === 'object'

        if (arguments.length === 1 && !isObj)
            return (this.elem[$jcData] || UniObj)[key];

        if (!this.elem[$jcData])
            this.elem[$jcData] = {};
        if (isObj) {
            var k;
            for (k in key) {
                if (key.hasOwnProperty(k)) {
                    this.elem[$jcData][k] = key[k]
                }
            }
        } else {
            this.elem[$jcData][key] = value;
        }
    },
    data: function() {
        return this._data("data")
    },
    _hasData: function(key) {
        return key in (this.elem[$jcData] || UniObj);
    },
    _removeData: function(key) {
        if (this._hasData(key))
            delete this.elem[$jcData][key];
    },
    html: function(text) {
        if (text) {
            this.$div.html(text);
        } else {
            return this.$div.html();
        }
    },
    hide: function() {
        this.$div.hide()
    },

    show: function() {
        this.$div.show()
    },

    empty: function() {
        this.$div.empty()
        if (this._data("id")) {
            this._removeData("frags")
            $watchPB(this._data("id")).unbind('$all')
            this._removeData("data")
            this.unbind("$all")
        }
    },
    publish: function(pStr, value) {
        if (arrPathVal(pStr, this.data()) != value)
            tempHelper.$pb(this._data('id')).publish(pStr, value)
    },
    listen: function(source, divArray, target) {
        //----全都是路径的判定以及param的预备处理

        var thisData = this.data(),
            id,
            sourceType = getType(source);

        if (sourceType === "object") {
            source = collectionPath(source)
        } else if (sourceType === 'string') {
            source = [source.split(".")]
            if (target) {
                target = [target.split(".")]
            }
        } else if (sourceType === 'array') {
            source = source.map(function(v) {
                return v.split(".")
            })
            if (target) {
                target = target.map(function(v) {
                    return v.split(".")
                })
            }
        }
        if (arguments.length < 3) {
            target = jc.mix(true, [], source)
        }
        if (!divArray) {
            divArray = [this.$div]
        } else if (getType(divArray) !== 'array') {
            divArray = [divArray]
        }
        //----全都是路径的判定以及param的预备处理---结束

        for (var i = 0, tem; tem = divArray[i++];) {
            tem = tempHelper(tem)
            id = tem._data("id")
            var targetData = tem.data()
            source.forEach(function(path, index) {
                var targetPath = target[index],
                    ref = arrPathRef(targetData, targetPath);
                if (Array.isArray(ref.obj)) {
                    var tpathStr = targetPath.join(".")
                    if (typeof ref.obj.set !== 'function') {
                        var tparentPath = targetPath.slice(0, targetPath.length - 1)
                        var tparentPathStr = tparentPath.join(".")
                        var parentPath = path.slice(0, path.length - 1)
                        var parentRef = arrPathRef(targetData, tparentPath);
                        valueBind(thisData, id, parentPath, tparentPath, parentRef.obj[parentRef.ref], parentRef)
                        ref.obj.$id = id
                        ref.obj.$parentPath = tparentPathStr
                        mix(ref.obj, fakeArrayMethod)

                    }
                    tempHelper.$pb(id).bind(tpathStr, (function(value) {
                        arrPathVal(thisData, this.path, value)
                    }).bind({
                        path: path
                    }))

                } else {
                    var val = ref.obj[ref.ref];
                    if (val instanceof Array) {
                        val.$id = id
                        val.$parentPath = targetPath.join(".")
                        mix(val, fakeArrayMethod)
                    }
                    valueBind(thisData, id, path, targetPath, val, ref)
                }

            })

        }
    },

    bind: function(eventName, fnName, fn) {
        var id = this._data("id")
        var type = getType(eventName)
        if (type === 'string')
            tempHelper.$pb(id).bind(eventName, fnName, fn)

        else if (type === 'object') {
            var collection = collectionPath(eventName)
            for (var i = 0, c; c = collection[i++];) {
                tempHelper.$pb(id).bind(c.join("."), fnName, fn)
            }
        } else {
            eventName.forEach(function(v) {
                tempHelper.$pb(id).bind(v, fnName, fn)
            })
        }
        this.listen(eventName)

    },
    unbind: function(eventName, fnName) {
        var id = this._data("id")
        var type = getType(eventName)
        if (type === 'string')
            tempHelper.$pb(id).unbind(eventName, fnName)
        else if (type === 'object') {
            var collection = collectionPath(eventName)
            for (var i = 0, c; c = collection[i++];) {
                tempHelper.$pb(id).unbind(c.join("."), fnName)
            }
        } else {
            eventName.forEach(function(v) {
                tempHelper.$pb(id).unbind(v, fnName)
            })
        }


    },
    reRender: function(id, data) {
        var frag = this._data("frags")[id];
        jc.mix(true, this.data(), data)
        //frag.elem.style.visibility = "hidden"
        frag.elem.innerHTML = frag.tpl
        scanAttr(frag.elem, data, this)
        //frag.elem.style.visibility = ""

        frag.elem.style.display = ""
    },
    //用正则是否会更好点？
    getCleanData: function(cleanData, originData) {
        if (cleanData === undefined) {
            cleanData = {}
        }
        if (originData === undefined)
            originData = this.data()
        switch (getType(originData)) {
            case "object":
                for (var key in originData) {
                    switch (key) {
                        case "$index":
                        case "$path":
                            break;
                        default:
                            switch (getType(originData[key])) {
                                case "object":
                                    cleanData[key] = {}
                                    this.getCleanData(cleanData[key], originData[key])
                                    break;
                                case "array":
                                    cleanData[key] = []
                                    this.getCleanData(cleanData[key], originData[key])
                                    break;
                                default:
                                    cleanData[key] = originData[key]
                            }
                    }
                }

                break;
            case "array":
                for (var i = 0; i < originData.length; i++) {
                    switch (getType(originData[i])) {
                        case "object":
                            cleanData.push({})
                            this.getCleanData(cleanData[i], originData[i])
                            break;
                        case "array":
                            cleanData.push([])
                            this.getCleanData(cleanData[i], originData[i])
                            break;
                        default:
                            cleanData.push(originData[i])
                    }
                }
                break;
        }
        return cleanData
    }
};

/**
 *
 *
 * 关于模板的操作，都基于此。
 *
 * @namespace tempHelper
 * @namespace tempHelper
 * @param  {Element} $div         jquery selected result
 * @return {object}             the utils for operating the template
 */
function tempHelper($div) {
    var re;
    if ($div.length !== 1) {
        tempError('the container selected by jqury {1} is not unique or not exists, please check', $div.selector || $div)
    }

    re = {
        $div: $div,
        elem: $div[0]
    };

    for (var key in tempHelperObj) {
        if (tempHelperObj.hasOwnProperty(key) /*&& key.search(/^_/ )*/ ) {
            re[key] = tempHelperObj[key]
        }
    }

    return re;
}

tempHelper.$pb = initPB();